﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Reflection;
using System.Collections;
using System.Xml.Linq;
using System.IO;
using System.Data.Objects;

namespace Infrastructure
{
    public class EFExpert:IDisposable
    {

            #region Constant
            private const string EntityState = "EntityState";
            private const string EntityKey = "EntityKey";
            private const string ConceptualModels = "ConceptualModels";
            private const string Namespace = "Namespace";
            private const string EntityType = "EntityType";
            private const string Name = "Name";
            private const string NavigationProperty = "NavigationProperty";
            private const string Association = "Association";
            private const string Relationship = "Relationship";
            private const string End = "End";
            private const string Role = "Role";
            private const string ToRole = "ToRole";
            private const string Multiplicity = "Multiplicity";
            private const string SetNavigation = "*";
            private const string RefNavigation = "0..1";
            private const string EntitySet = "EntitySet";
            private const string csdl = ".csdl";
            private const string ssdl = ".ssdl";
            private const string msl = ".msl";
            private const string DefaultContainerName = "DefaultContainerName";

            #endregion

            private static EFExpert expert;

            #region Dependency
            private XmlParser xmlParser
            {
                set;
                get;
            }

            private Reflector reflector
            {
                set;
                get;
            }
        #endregion

        private XDocument CXmlDocument;
        private XDocument SXmlDocument;
        private XDocument MXmlDocument;

        private void initialize(ObjectContext context)
        {
            reflector = Reflector.Instance();
            Stream stream = GetObjectContextXMLStream(context, 1);
            LoadParser(stream);
            SXmlDocument = xmlParser.XmlDocument;
            stream = GetObjectContextXMLStream(context, 2);
            LoadParser(stream);
            CXmlDocument = xmlParser.XmlDocument;
            stream = GetObjectContextXMLStream(context, 3);
            LoadParser(stream);
            MXmlDocument = xmlParser.XmlDocument;
           
        }

        private void LoadParser(Stream xmlStream)
        {
            xmlParser = XmlParser.Instance(xmlStream);
        }

     
        public static EFExpert Instance(ObjectContext context)
        {
            if (expert == null)
            {
                expert = new EFExpert();
                expert.initialize(context);
            }
            return expert;
        }


        public EntityState GetObjectState(object entity)
        {
            if (entity is EntityObject)
            {
                object state =reflector.GetPropertyValue(entity, EntityState);
                return (EntityState)state;
            }
            else
                throw new Exception("object must be EntityObject.");
        }

        public EntityKey GetEntityKey(object entity)
        {
            if (entity is EntityObject)
            {
                object key = reflector.GetPropertyValue(entity, EntityKey);
                return (EntityKey)key;
 
            }
            else
                throw new Exception("object must be EntityObject.");
        }

        /// <summary>
        /// Return children in an entity
        /// </summary>
        /// <param name="entity"></param>
        /// <param name="state">If state=null,return all childen,
        ///                     Otherwise, return chilren in specified state</param>
        /// <returns></returns>
        public void  GetChildrenEntities(object entity,EntityState? state,List<object> children)
        {
            IList<PropertyInfo> entityCollections = this.GetSetNavigationProperties(entity);
            foreach (PropertyInfo p in entityCollections)
            {
                IEnumerable childrenGrp = reflector.GetPropertyList<object>(entity, p.Name);
                foreach (var v in childrenGrp)
                {
                    GetChildrenEntities(v, state, children);
                    if (state == null || GetObjectState(v) == state)
                        children.Add(v);
                }
            }
        }

        public void FillEntityWithChildren(object entity)
        {
            System.Data.EntityState state=GetObjectState(entity);
            if (state == System.Data.EntityState.Unchanged || state == System.Data.EntityState.Modified)
            {
                List<PropertyInfo> setProperties = GetSetNavigationProperties(entity);
                foreach (PropertyInfo p in setProperties)
                {
                    object objectQuery = reflector.GetPropertyValue(entity, p);
                    reflector.InvokeNoParameters(objectQuery, "Load", new object[] { MergeOption.OverwriteChanges });
                }
            }
            else
                throw new Exception("Unchanged or Modified EntityState is supported only in this operation!");
        }


        public string GetConceptualNameSpace(object entity)
        {
            List<XElement> elementInCsdl = CXmlDocument.Elements().ToList();
            string objName = entity.GetType().Name;
            string csdlNameSpace = (from ei in elementInCsdl
                                    select ei.Attribute(Namespace).Value).FirstOrDefault();
            return csdlNameSpace;
        }

        private List<PropertyInfo> GetNavigationProperties(object entity,string navigation)
        {
            List<XElement> elementInCsdl = CXmlDocument.Elements().ToList(); ;
            string objName = entity.GetType().Name;
            string csdlNameSpace = GetConceptualNameSpace(entity);

            //Utilize the relativity of the both set

            List<string> setNavigation = (from e in
                                              (from ei in elementInCsdl
                                               from eei in ei.Elements()
                                               where eei.Name.LocalName == EntityType && eei.Attribute(Name).Value == objName
                                               from n in eei.Elements()
                                               where n.Name.LocalName == NavigationProperty
                                               select n)
                                          where
                                          (
                                              (from a in
                                                   (from ei in elementInCsdl
                                                    from eei in ei.Elements()
                                                    where eei.Name.LocalName == Association
                                                    select eei).Where(aa => aa.Attribute(Name).Value == e.Attribute(Relationship).Value.Replace(csdlNameSpace + ".", ""))
                                               from r in a.Elements()
                                               where r.Name.LocalName == End && r.Attribute(Role).Value == e.Attribute(ToRole).Value && r.Attribute(Multiplicity).Value == navigation
                                               select r).Count() > 0
                                          )
                                          select e.Attribute(Name).Value).ToList();

            List<PropertyInfo> setProperties = new List<PropertyInfo>();
            foreach (string str in setNavigation)
            {
                Type type = entity.GetType();
                setProperties.Add(type.GetProperty(str));
            }
            return setProperties;
        }

        public List<PropertyInfo> GetRefNavigationProperties(object entity)
        {
            return GetNavigationProperties(entity, RefNavigation);
        }

        public List<PropertyInfo> GetSetNavigationProperties(object entity)
        {
            return GetNavigationProperties(entity, SetNavigation);
        }

        public string GetEntitySetName(object entity)
        {
            string nameSpace=GetConceptualNameSpace(entity);
            string typeName=entity.GetType().Name;

            string entitySetName = (from e in CXmlDocument.Descendants()
                                    where e.Name.LocalName == EntitySet
                                    && e.Attribute(EntityType).Value == nameSpace + "." + typeName
                                    select e.Attribute(Name).Value).FirstOrDefault();
            return entitySetName;
        }

        public void Dispose()
        {
            xmlParser.Dispose();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="objectContext"></param>
        /// <param name="xmlType">1:SSDL;2:CSDL;3:MSL</param>
        /// <returns></returns>
        private Stream GetObjectContextXMLStream(object objectContext,int xmlType)
        {
            string containerName = reflector.GetPropertyValue(objectContext, DefaultContainerName).ToString();
            string type = string.Empty;
            switch (xmlType)
            {
                case 1:
                    type = ssdl;
                    break;
                case 2:
                    type = csdl;
                    break;
                case 3:
                    type = msl;
                    break;
            }
            Stream stream = Reflector.Instance().GetManifestResource(containerName + type, objectContext.GetType());
            if (stream == null)
                throw new Exception("Can not load EDM XML file.");
            return stream;
        }
    }
}
